/*
 * Copyright 2002-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package my.learn.common.web.core.annotation;


import my.learn.common.web.core.ReflexUtils;
import my.learn.common.web.core.exception.AssertFactory;
import my.learn.common.web.core.exception.RuntimeExceptionFactory;
import my.learn.common.web.core.utils.ObjectUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * {@link InvocationHandler} for an {@link Annotation} that Spring has
 * <em>synthesized</em> (i.e. wrapped in a dynamic proxy) with additional
 * functionality such as attribute alias handling.
 *
 * @param <A> the annotationElement type
 * @author Sam Brannen
 * @author Phillip Webb
 * @see Annotation
 * @since 5.2
 */
final class SynthesizedMergedAnnotationElementInvocationHandler<A extends Annotation> implements InvocationHandler {

    private final MergedAnnotationElement annotationElement;

    private final Class<A> type;

//    private final AttributeMethods attributes;

    private final Map<String, Object> valueCache = new ConcurrentHashMap<>(8);

    private volatile Integer hashCode;

    private volatile String string;


    private SynthesizedMergedAnnotationElementInvocationHandler(MergedAnnotationElement annotationElement, Class<A> type) {
        AssertFactory.notNull(annotationElement, "MergedAnnotation must not be null");
        AssertFactory.notNull(type, "Type must not be null");
        AssertFactory.isFalse(type.isAnnotation(), "Type must be an annotationElement");
        this.annotationElement = annotationElement;
        this.type = type;
//        this.attributes = AttributeMethods.forAnnotationType(type);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
//        if (ReflexUtils.isEqualsMethod(method)) {
//            return annotationEquals(args[0]);
//            return false;
//        }
//        if (ReflexUtils.isHashCodeMethod(method)) {
//            return annotationHashCode();
//        }
        if (ReflexUtils.isToStringMethod(method)) {
            return toString(type);
        }
        if (isAnnotationTypeMethod(method)) {
            return this.type;
        }
        return getAttributeValue(method);

//        RuntimeExceptionFactory.of(String.format(
//                "Method [%s] is unsupported for synthesized annotationElement type [%s]", method, this.type)
//        );
    }

    private boolean isAnnotationTypeMethod(Method method) {
        return (method.getName().equals("annotationType") && method.getParameterCount() == 0);
    }


    /**
     * This method currently does not address the following issues which we may
     * choose to address at a later point in time.
     *
     * <ul>
     * <li>non-ASCII, non-visible, and non-printable characters within a character
     * or String literal are not escaped.</li>
     * <li>formatting for float and double values does not take into account whether
     * a value is not a number (NaN) or infinite.</li>
     * </ul>
     *
     * @param value the attribute value to format
     * @return the formatted string representation
     */
    private String toString(Class<A> value) {
        return value.toString();
    }

    private Object getAttributeValue(Method method) {
        Object value = this.valueCache.computeIfAbsent(method.getName(), attributeName -> {
            Class<?> type = method.getReturnType();
//            return this.annotationElement.getValue(attributeName, type).orElseThrow(
//                    () -> new NoSuchElementException("No value found for attribute named '" + attributeName +
//                            "' in merged annotationElement " + this.annotationElement.getType().getName()))
            return getAttributeValue(List.of(annotationElement), method);

        });
        return value;
    }

    /**
     * 通过多个元素获取 属性
     *
     * @param mergedAnnotationElementList 多个元素
     * @param method                      属性
     * @return
     */
    private Object getAttributeValue(List<MergedAnnotationElement> mergedAnnotationElementList, Method method) {
        for (MergedAnnotationElement mergedAnnotationElement : mergedAnnotationElementList) {
            // 恰巧命中到当前注解
            if (!ObjectUtils.isEmpty(mergedAnnotationElement.getAnnotationMethodList()) &&
                    mergedAnnotationElement.getAnnotationMethodList().stream().anyMatch(method1 -> method1.equals(method))) {
                AnnotatedElement element = mergedAnnotationElement.getElement();

                Annotation source = mergedAnnotationElement.getSource();
                return ReflexUtils.invokeDeclaredMethod(source, method);
            }
            if (ObjectUtils.isEmpty(mergedAnnotationElement.getAnnotationMethodList())) {
                Object attributeValue = getAttributeValue(mergedAnnotationElement.getMergedAnnotationClassList(), method);
                if (attributeValue != null) {
                    return attributeValue;
                } else {
                    continue;
                }
            } else {
                // 递归
                String targetMethodName = method.getName();
                List<Method> aliasForMethodList = mergedAnnotationElement.getAnnotationMethodList()
                        .stream()
                        .filter(annotationMethod -> {
                            String annotationMethodName = annotationMethod.getName();

                            AliasFor aliasFor = annotationMethod.getAnnotation(AliasFor.class);
                            if (aliasFor != null && aliasFor.annotation().equals(method.getDeclaringClass())) {
                                if (aliasFor.attribute().equals(targetMethodName)
                                        || aliasFor.value().equals(method.getName())) {
                                    return true;
                                } else if (ObjectUtils.isEmpty(aliasFor.attribute()) && ObjectUtils.isEmpty(aliasFor.value())) {
                                    return targetMethodName.equals(annotationMethodName);
                                }else {
                                    return false;
                                }
                            } else {
                                return false;
                            }
                        })
                        .toList();
                if (aliasForMethodList.isEmpty()) {
                    // 递归
                    Object attributeValue = getAttributeValue(mergedAnnotationElement.getMergedAnnotationClassList(), method);
                    if (attributeValue != null) {
                        return attributeValue;
                    }
                } else if (aliasForMethodList.size() != 1) {
                    RuntimeExceptionFactory.of("choose value  " + targetMethodName + " from the annotation of " +
                            mergedAnnotationElement.getElement() +
                            " has more same method of " + aliasForMethodList.stream().map(Method::getName).collect(Collectors.joining(","))
                    );
                } else {
                    // 命中
                    Method targetMethod = aliasForMethodList.get(0);
                    AnnotatedElement element = mergedAnnotationElement.getElement();
                    Annotation source = mergedAnnotationElement.getSource();
                    return ReflexUtils.invokeDeclaredMethod(source, targetMethod);
                }
            }

        }

        return null;
    }

    /**
     * Clone the provided array, ensuring that the original component type is retained.
     *
     * @param array the array to clone
     */
    private Object cloneArray(Object array) {
        if (array instanceof boolean[] booleans) {
            return booleans.clone();
        }
        if (array instanceof byte[] bytes) {
            return bytes.clone();
        }
        if (array instanceof char[] chars) {
            return chars.clone();
        }
        if (array instanceof double[] doubles) {
            return doubles.clone();
        }
        if (array instanceof float[] floats) {
            return floats.clone();
        }
        if (array instanceof int[] ints) {
            return ints.clone();
        }
        if (array instanceof long[] longs) {
            return longs.clone();
        }
        if (array instanceof short[] shorts) {
            return shorts.clone();
        }

        // else
        return ((Object[]) array).clone();
    }

    @SuppressWarnings("unchecked")
    static <A extends Annotation> A createProxy(MergedAnnotationElement annotationElement, Class<A> type) {
        ClassLoader classLoader = type.getClassLoader();
        Class<?>[] interfaces = new Class<?>[]{type};
        InvocationHandler handler = new SynthesizedMergedAnnotationElementInvocationHandler<>(annotationElement, type);
        return (A) Proxy.newProxyInstance(classLoader, interfaces, handler);
    }

    private static String getName(Class<?> clazz) {
        String canonicalName = clazz.getCanonicalName();
        return (canonicalName != null ? canonicalName : clazz.getName());
    }

}
